# Copyright (c) 2019 Presto Labs Pte. Ltd.
# Author: jaewon

import logging

from coin.base.datetime_util import to_datetime
from coin.proto import coin_order_enums_pb2
from coin.strategy.mm.subscription import SubscriptionRequest, FeedSubscriptionRequest
from coin.strategy.marketsim.core import marketsim
from coin.experimental.jaewon.strat.pass_model import PassModel

# Config
from coin.proto.coin_executor_pb2 import PassiveExecutorConfig
from coin.experimental.jaewon.strat.pass_model import PassModelConfig
from coin.experimental.jaewon.strat.pass_model_strategy import PassModelStrategyConfig


class _Executor:
  def __init__(self,
               order_gateway,
               product,
               sub_req,
               product_sim_spec,
               executor_params,
               verbose=False):
    self._og = order_gateway
    self._og.add_product(product, product_sim_spec)
    self._og.add_execution_callback(self._on_execution)

    self._product = product
    self._sub_req = sub_req

    self._exe_params = executor_params
    self._lot_size = executor_params.lot_size
    self._min_pos = executor_params.min_pos
    self._max_pos = executor_params.max_pos
    self._min_fire_period = executor_params.order_update_period
    self._post_only = executor_params.post_only

    self._verbose = verbose

    self._pos = 0.
    self._book = None
    self._last_post_ts = 0

  def prepare(self, feed_subsystem):
    sub_req = self._sub_req
    if isinstance(sub_req, str):
      sub_req = SubscriptionRequest.from_str(self._sub_req)

    feed_sub_req = FeedSubscriptionRequest.create([self._product], sub_req)
    feed_subsystem.subscribe_fastfeed(feed_sub_req, self._on_book_reset)

  def manage_mm_orders(self,
                       *,
                       timestamp,
                       sell_price,
                       buy_price,
                       sell_price_pull=None,
                       buy_price_pull=None):
    if (self._book is None or timestamp - self._last_post_ts < self._min_fire_period):
      return

    self._og.cancel_all()

    if self._pos < self._max_pos:
      qty = min(self._max_pos - self._pos, self._lot_size)
      self._og.submit(product=self._product,
                      side=coin_order_enums_pb2.BUY_ORDER,
                      price=buy_price,
                      qty=qty,
                      post_only=self._post_only)
      if self._verbose:
        print('BUY %f %f (%f)' %
              (buy_price,
               self._book.bid0().price,
               (self._book.bid0().price - buy_price) / self._book.bid0().price * 10000))

    if self._min_pos < self._pos:
      qty = min(self._pos - self._min_pos, self._lot_size)
      self._og.submit(product=self._product,
                      side=coin_order_enums_pb2.SELL_ORDER,
                      price=sell_price,
                      qty=qty,
                      post_only=self._post_only)
      if self._verbose:
        print('SELL %f %f (%f)' %
              (sell_price,
               self._book.ask0().price,
               (sell_price - self._book.ask0().price) / self._book.ask0().price * 10000))

    self._last_post_ts = timestamp

  def get_pos(self):
    return self._pos

  def _on_execution(self, execution):
    self._pos = execution.position

  def _on_book_reset(self, sub_req, book_builder):
    if sub_req == SubscriptionRequest.from_str(self._sub_req):
      book_builder.subscribe_book(self._product, self._on_book)

  def _on_book(self, book):
    self._book = book


class PassModelSimStrategy:
  def __init__(self,
               *,
               name,
               machine,
               config,
               trade_product_sim_spec,
               sim_spec=None,
               ref_time=None,
               logger=None):
    super().__init__()

    self._logger = logger or logging.getLogger('PassModelStrategy')
    self._config = config
    self._name = name

    if ref_time is not None:
      self._config.model_config.ref_time = to_datetime(ref_time)

    assert str(self._config.trade_product) == trade_product_sim_spec.symbol
    self._msim = marketsim.MarketSim(sim_spec=sim_spec, default_machine=machine)
    self._executor = _Executor(self._msim,
                               self._config.trade_product,
                               self._config.trade_sub_req,
                               trade_product_sim_spec,
                               self._config.executor_params)
    self._model = None

  def prepare(self, feed_subsystem):
    self._model = PassModel(self._config.model_config,
                            pos_getter=self._executor.get_pos,
                            trigger_callback=self.on_model_trigger)

    feed_subsystem.subscribe_fastfeed(self._msim.get_feed_sub_reqs(), self._msim.on_book_reset)
    self._model.prepare(feed_subsystem)
    self._executor.prepare(feed_subsystem)

  def on_model_trigger(self):
    self._executor.manage_mm_orders(timestamp=self._model.timestamp,
                                    sell_price=self._model.sell_price,
                                    buy_price=self._model.buy_price,
                                    sell_price_pull=self._model.sell_price_pull,
                                    buy_price_pull=self._model.buy_price_pull)

  @property
  def name(self):
    return self._name

  @property
  def sim(self):
    return self._msim

  def get_sim_stat(self):
    return self._msim.generate_sim_stat()
